Imports Microsoft.VisualBasic
Imports System
Imports System.Collections.Generic
Imports System.Text
Imports System.Xml
Imports System.ComponentModel
Imports System.Globalization
Imports C4F.DevKit.WebServices.Datasets

Namespace C4F.DevKit.WebServices
	''' <summary>
	''' This class is a wrapper over Yahoo traffic service.
	''' </summary>
	<ToolboxItem(True)> _
	Public Class YahooTraffic
		Inherits C4FWebServicesBase
		#Region "Private members"
		''' <summary>
		''' Base Url
		''' </summary>
        Private Shared baseUrl As String = My.Resources.YahooTrafficBaseUrl

		''' <summary>
		''' Application Key required to use the Api
		''' </summary>
		Private applicationKey_Renamed As String = String.Empty

		#End Region

		#Region "Accessors"

		''' <summary>
		''' Gets/sets application key for yahoo traffic service.
		''' </summary>
		<Category("YahooTraffic"), Description("Gets/sets application key for yahoo traffic service.")> _
		Public Property ApplicationKey() As String
			Get
				Return Me.applicationKey_Renamed
			End Get
			Set(ByVal value As String)
				If value Is Nothing Then
					Me.applicationKey_Renamed = String.Empty
				Else
					Me.applicationKey_Renamed = value.Trim()
				End If
			End Set
		End Property

		#End Region

		#Region "Constructors"
		''' <summary>
		''' Initializes a new object of YahooTraffic class
		''' </summary>
		Public Sub New()
		End Sub
		''' <summary>
		''' Initializes a new object of YahooTraffic class with an application key
		''' </summary>
		''' <param name="appKey">Application key</param>
		Public Sub New(ByVal appKey As String)
			Me.ApplicationKey = appKey
		End Sub

		#End Region

		#Region "Public functions"
		''' <summary>
		''' Get traffic alert information from a given location
		''' </summary>
		''' <param name="street">Name of the street</param>
		''' <param name="city">Name of the city</param>
		''' <param name="state">The United States state</param>
		''' <param name="zip">Zip code of the location</param>
		''' <param name="location">If location is specified, it will take priority over the individual fields in determining the location for the query</param>
		''' <param name="latitude">he latitude of the starting location</param>
		''' <param name="longitude">The longitude of the starting location. If both latitude and longitude are specified, they will take priority over all other location data</param>
		''' <param name="severity">The minimum severity level to return, from 1 (least severe) to 5 (most severe)</param>
		''' <param name="zoom">The zoom level for the data, from 1 (street level) to 12 (country level)</param>
		''' <param name="radius">How far (in miles) from the specified location to provide data for</param>
		''' <param name="include_map">If set to 1 will return the URL of a map image for the location of each reported incident</param>
		''' <param name="image_type">The image format for the map</param>
		''' <param name="image_height">The height of the image</param>
		''' <param name="image_width">The width of the image</param>
		''' <returns>The traffic incidents matching the criteria</returns>
		''' <exception cref="InvalidOperationException">The Application Key is not set</exception>
		Public Function GetTraffic(ByVal street As String, ByVal city As String, ByVal state As State, ByVal zip As String, ByVal location As String, ByVal latitude As Single, ByVal longitude As Single, ByVal severity As Nullable(Of Integer), ByVal zoom As Nullable(Of Integer), ByVal radius As Integer, ByVal include_map As Nullable(Of Integer), ByVal image_type As String, ByVal image_height As Nullable(Of Integer), ByVal image_width As Nullable(Of Integer)) As List(Of TrafficIncident)
			If String.IsNullOrEmpty(Me.ApplicationKey) Then
				Throw New InvalidOperationException(My.Resources.ErrorApplicationKeyNotSet)
			End If

			Dim requestUrl As String = Me.GetQueryString(street, city, state, zip, location, latitude, longitude, severity, zoom, radius, include_map, image_type, image_height, image_width)

			' Get traffic responses
			Dim responseData As String = MyBase.GetQueryResponse(requestUrl)

			' Parse the data and return results
			Return YahooTraffic.ParseResponseXml(responseData)
		End Function

		''' <summary>
		''' Get traffic alert information from a given location
		''' </summary>
		''' <param name="street">Street name</param>
		''' <param name="city">City name</param>
		''' <param name="state">The United States state</param>
		''' <returns>The traffic incidents matching the criteria</returns>
		''' <exception cref="InvalidOperationException">The Application Key is not set</exception>
		Public Function GetTraffic(ByVal street As String, ByVal city As String, ByVal state As State) As List(Of TrafficIncident)
			If String.IsNullOrEmpty(Me.ApplicationKey) Then
				Throw New InvalidOperationException(My.Resources.ErrorApplicationKeyNotSet)
			End If

			Dim requestUrl As String = Me.GetQueryString(street, city, state, String.Empty, String.Empty, 0, 0, Nothing, Nothing, 0, Nothing, Nothing, Nothing, Nothing)

			' Get traffic responses
			Dim responseData As String = MyBase.GetQueryResponse(requestUrl)

			' Parse the data and return results
			Return YahooTraffic.ParseResponseXml(responseData)
		End Function

		''' <summary>
		''' Get traffic alert information from a given location
		''' </summary>
		''' <param name="location">The location can be 'city, state', 'city, state, zip', 'zip', 'street, city, state', 'street, city, state, zip' or 'street, zip'</param>
		''' <returns>The traffic incidents matching the criteria</returns>
		''' <exception cref="InvalidOperationException">The Application Key is not set</exception>
		Public Function GetTraffic(ByVal location As String) As List(Of TrafficIncident)
			If String.IsNullOrEmpty(Me.ApplicationKey) Then
                Throw New InvalidOperationException(My.Resources.ErrorApplicationKeyNotSet)
			End If

			Dim requestUrl As String = Me.GetQueryString(String.Empty, String.Empty, State._AllStates, String.Empty, location, 0, 0, Nothing, Nothing, 0, Nothing, Nothing, Nothing, Nothing)

			' Get traffic responses
			Dim responseData As String = MyBase.GetQueryResponse(requestUrl)

			' Parse the data and return results
			Return YahooTraffic.ParseResponseXml(responseData)
		End Function

		''' <summary>
		''' Get traffic alert information from a given location
		''' </summary>
		''' <param name="location">The location can be 'city, state', 'city, state, zip', 'zip', 'street, city, state', 'street, city, state, zip' or 'street, zip'</param>
		''' <returns>Dataset containing the traffic incidents matching the criteria</returns>
		''' <exception cref="InvalidOperationException">The Application Key is not set</exception>
		Public Function GetTraffic_DS(ByVal location As String) As DS_YahooTraffic
			Dim incidents As List(Of TrafficIncident) = GetTraffic(location)
			Dim dataset As DS_YahooTraffic = New DS_YahooTraffic()

			For Each incident As TrafficIncident In incidents
				Dim locationRow As DS_YahooTraffic.LocationRow = dataset.Location.AddLocationRow(incident.IncidentDetails.Location.Latitude, incident.IncidentDetails.Location.Longitude)
				Dim incidentDetailsRow As DS_YahooTraffic.IncidentDetailsRow = dataset.IncidentDetails.AddIncidentDetailsRow(incident.IncidentDetails.Direction, incident.IncidentDetails.Severity, incident.IncidentDetails.ReportDate, incident.IncidentDetails.UpdateDate, incident.IncidentDetails.EndDate)
				dataset.TrafficIncident.AddTrafficIncidentRow(incident.Type, incident.Description, incident.ImageUrl, locationRow, incidentDetailsRow)
			Next incident
			Return dataset
		End Function

		''' <summary>
		''' Get traffic alert information from a given location
		''' </summary>
		''' <param name="street">Street name</param>
		''' <param name="city">City name</param>
		''' <param name="state">The United States state</param>
		''' <returns>Dataset containing the traffic incidents matching the criteria</returns>
		''' <exception cref="InvalidOperationException">The Application Key is not set</exception>
		Public Function GetTraffic_DS(ByVal street As String, ByVal city As String, ByVal state As State) As DS_YahooTraffic
			Dim incidents As List(Of TrafficIncident) = GetTraffic(street, city, state)
			Dim dataset As DS_YahooTraffic = New DS_YahooTraffic()

			For Each incident As TrafficIncident In incidents
				Dim locationRow As DS_YahooTraffic.LocationRow = dataset.Location.AddLocationRow(incident.IncidentDetails.Location.Latitude, incident.IncidentDetails.Location.Longitude)
				Dim incidentDetailsRow As DS_YahooTraffic.IncidentDetailsRow = dataset.IncidentDetails.AddIncidentDetailsRow(incident.IncidentDetails.Direction, incident.IncidentDetails.Severity, incident.IncidentDetails.ReportDate, incident.IncidentDetails.UpdateDate, incident.IncidentDetails.EndDate)
				dataset.TrafficIncident.AddTrafficIncidentRow(incident.Type, incident.Description, incident.ImageUrl, locationRow, incidentDetailsRow)
			Next incident
			Return dataset
		End Function

		''' <summary>
		''' Get traffic alert information from a given location
		''' </summary>
		''' <param name="street">Name of the street</param>
		''' <param name="city">Name of the city</param>
		''' <param name="state">The United States state</param>
		''' <param name="zip">Zip code of the location</param>
		''' <param name="location">If location is specified, it will take priority over the individual fields in determining the location for the query</param>
		''' <param name="latitude">he latitude of the starting location</param>
		''' <param name="longitude">The longitude of the starting location. If both latitude and longitude are specified, they will take priority over all other location data</param>
		''' <param name="severity">The minimum severity level to return, from 1 (least severe) to 5 (most severe)</param>
		''' <param name="zoom">The zoom level for the data, from 1 (street level) to 12 (country level)</param>
		''' <param name="radius">How far (in miles) from the specified location to provide data for</param>
		''' <param name="include_map">If set to 1 will return the URL of a map image for the location of each reported incident</param>
		''' <param name="image_type">The image format for the map</param>
		''' <param name="image_height">The height of the image</param>
		''' <param name="image_width">The width of the image</param>
		''' <returns>Dataset containing the traffic incidents matching the criteria</returns>
		''' <exception cref="InvalidOperationException">The Application Key is not set</exception>
		Public Function GetTraffic_DS(ByVal street As String, ByVal city As String, ByVal state As State, ByVal zip As String, ByVal location As String, ByVal latitude As Single, ByVal longitude As Single, ByVal severity As Nullable(Of Integer), ByVal zoom As Nullable(Of Integer), ByVal radius As Integer, ByVal include_map As Nullable(Of Integer), ByVal image_type As String, ByVal image_height As Nullable(Of Integer), ByVal image_width As Nullable(Of Integer)) As DS_YahooTraffic
			Dim incidents As List(Of TrafficIncident) = GetTraffic(street, city, state, zip, location, latitude, longitude, severity, zoom, radius, include_map, image_type, image_height, image_width)
			Dim dataset As DS_YahooTraffic = New DS_YahooTraffic()

			For Each incident As TrafficIncident In incidents
				Dim locationRow As DS_YahooTraffic.LocationRow = dataset.Location.AddLocationRow(incident.IncidentDetails.Location.Latitude, incident.IncidentDetails.Location.Longitude)
				Dim incidentDetailsRow As DS_YahooTraffic.IncidentDetailsRow = dataset.IncidentDetails.AddIncidentDetailsRow(incident.IncidentDetails.Direction, incident.IncidentDetails.Severity, incident.IncidentDetails.ReportDate, incident.IncidentDetails.UpdateDate, incident.IncidentDetails.EndDate)
				dataset.TrafficIncident.AddTrafficIncidentRow(incident.Type, incident.Description, incident.ImageUrl, locationRow, incidentDetailsRow)
			Next incident
			Return dataset
		End Function

		#End Region

		#Region "Private functions"

		''' <summary>
		''' Get request Url
		''' </summary>
		''' <param name="street">Street name</param>
		''' <param name="city">City name</param>
		''' <param name="state">The United States state</param>
		''' <param name="zip">Zip code</param>
		''' <param name="location">If location is specified, it will take priority over the individual fields in determining the location for the query</param>
		''' <param name="latitude">he latitude of the starting location</param>
		''' <param name="longitude">The longitude of the starting location. If both latitude and longitude are specified, they will take priority over all other location data</param>
		''' <param name="severity">The minimum severity level to return, from 1 (least severe) to 5 (most severe)</param>
		''' <param name="zoom">The zoom level for the data, from 1 (street level) to 12 (country level)</param>
		''' <param name="radius">How far (in miles) from the specified location to provide data for</param>
		''' <param name="include_map">If set to 1 will return the URL of a map image for the location of each reported incident</param>
		''' <param name="image_type">The image format for the map</param>
		''' <param name="image_height">The height of the image</param>
		''' <param name="image_width">The width of the image</param>
		''' <returns>Request Url</returns>
		Private Function GetQueryString(ByVal street As String, ByVal city As String, ByVal state As State, ByVal zip As String, ByVal location As String, ByVal latitude As Single, ByVal longitude As Single, ByVal severity As Nullable(Of Integer), ByVal zoom As Nullable(Of Integer), ByVal radius As Integer, ByVal include_map As Nullable(Of Integer), ByVal image_type As String, ByVal image_height As Nullable(Of Integer), ByVal image_width As Nullable(Of Integer)) As String
			'TODO: Add business validations here

			Dim queryBuilder As StringBuilder = New StringBuilder(YahooTraffic.baseUrl)
			' Add application key
			queryBuilder.Append("?appid=")
			queryBuilder.Append(Me.ApplicationKey)
			' Add street
			queryBuilder.Append("&street=")
			queryBuilder.Append(street)
			' Add city
			queryBuilder.Append("&city=")
			queryBuilder.Append(city)
			' Add state
			queryBuilder.Append("&state=")
			queryBuilder.Append(System.Enum.GetName(GetType(StateAbbreviation), state))
			' Add zip
			queryBuilder.Append("&zip=")
			queryBuilder.Append(zip)
			' Add location
			queryBuilder.Append("&location=")
			queryBuilder.Append(location)

			If latitude <> 0 AndAlso longitude <> 0 Then
				' Add latitude
				queryBuilder.Append("&latitude=")
				queryBuilder.Append(latitude.ToString(CultureInfo.InvariantCulture))
				' Add longitude
				queryBuilder.Append("&longitude=")
				queryBuilder.Append(longitude.ToString(CultureInfo.InvariantCulture))
			End If
			' Add severity
			If severity.HasValue Then
				queryBuilder.Append("&severity=")
				queryBuilder.Append(severity.Value.ToString(CultureInfo.InvariantCulture))
			End If
			' Add zoom
			If zoom.HasValue Then
				queryBuilder.Append("&zoom=")
				queryBuilder.Append(zoom.Value.ToString(CultureInfo.InvariantCulture))
			End If
			' Add radius
			If radius <> 0 Then
				queryBuilder.Append("&radius=")
				queryBuilder.Append(radius.ToString(CultureInfo.InvariantCulture))
			End If
			' Add include_map
			If include_map.HasValue Then
				queryBuilder.Append("&include_map=")
				queryBuilder.Append(include_map.ToString())
			End If
			' Add image_type
			If Not image_type Is Nothing Then
				queryBuilder.Append("&image_type=")
				queryBuilder.Append(image_type.ToString())
			End If
			' Add image_height
			If image_height.HasValue Then
				queryBuilder.Append("&image_height=")
				queryBuilder.Append(image_height.ToString())
			End If
			' Add image_width
			If image_width.HasValue Then
				queryBuilder.Append("&image_width=")
				queryBuilder.Append(image_width.ToString())
			End If

			Return queryBuilder.ToString()
		End Function

		''' <summary>
		''' Parses the response data and extracts the traffic incidents
		''' </summary>
		''' <param name="responseData">Response data</param>
		''' <returns>A list of traffic incidents</returns>
		Private Shared Function ParseResponseXml(ByVal responseData As String) As List(Of TrafficIncident)
			Dim xmlDocument As XmlDocument = New XmlDocument()
			Dim trafficIncidents As List(Of TrafficIncident) = New List(Of TrafficIncident)()
			Dim nsmanager As XmlNamespaceManager = New XmlNamespaceManager(xmlDocument.NameTable)
			nsmanager.AddNamespace("maps", "urn:yahoo:maps")

			xmlDocument.LoadXml(responseData)
			' Retrieve result nodes
			Dim trafficIncidentNodes As XmlNodeList = xmlDocument.DocumentElement.SelectNodes("maps:Result", nsmanager)

			If trafficIncidentNodes.Count = 0 Then
				Return trafficIncidents
			End If

			' Set a base date to enable conversion of Unix timestamps returned in xml to DateTime object
			Dim baseDateTime As System.DateTime = New System.DateTime(1970, 1, 1, 0, 0, 0, 0)

			Dim trafficIncident As TrafficIncident = Nothing
			For Each trafficIncidentNode As XmlNode In trafficIncidentNodes
				trafficIncident = New TrafficIncident()
				trafficIncident.IncidentDetails = New IncidentDetails()
				trafficIncident.IncidentDetails.Location = New Location()
				' Populate traffic incident object
				trafficIncident.Type = trafficIncidentNode.Attributes("type").Value
				trafficIncident.Description = trafficIncidentNode.SelectSingleNode("maps:Description", nsmanager).InnerText
				trafficIncident.IncidentDetails.Location.Latitude = Single.Parse(trafficIncidentNode.SelectSingleNode("maps:Latitude", nsmanager).InnerText, CultureInfo.InvariantCulture)
				trafficIncident.IncidentDetails.Location.Longitude = Single.Parse(trafficIncidentNode.SelectSingleNode("maps:Longitude", nsmanager).InnerText, CultureInfo.InvariantCulture)
				trafficIncident.IncidentDetails.Direction = trafficIncidentNode.SelectSingleNode("maps:Direction", nsmanager).InnerText
				trafficIncident.IncidentDetails.Severity = Convert.ToInt32(trafficIncidentNode.SelectSingleNode("maps:Severity", nsmanager).InnerText, CultureInfo.InvariantCulture)
				trafficIncident.IncidentDetails.ReportDate = baseDateTime.AddSeconds(Convert.ToDouble(trafficIncidentNode.SelectSingleNode("maps:ReportDate", nsmanager).InnerText, CultureInfo.InvariantCulture))
				trafficIncident.IncidentDetails.UpdateDate = baseDateTime.AddSeconds(Convert.ToDouble(trafficIncidentNode.SelectSingleNode("maps:UpdateDate", nsmanager).InnerText, CultureInfo.InvariantCulture))
				trafficIncident.IncidentDetails.EndDate = baseDateTime.AddSeconds(Convert.ToDouble(trafficIncidentNode.SelectSingleNode("maps:EndDate", nsmanager).InnerText, CultureInfo.InvariantCulture))

				Dim imageUrlNode As XmlNode = trafficIncidentNode.SelectSingleNode("maps:ImageUrl", nsmanager)

				If Not imageUrlNode Is Nothing Then
					trafficIncident.ImageUrl = imageUrlNode.InnerText
				End If

				trafficIncidents.Add(trafficIncident)
			Next trafficIncidentNode
			Return trafficIncidents
		End Function
		#End Region
	End Class

	''' <summary>
	''' This class stored information related to traffic information.
	''' </summary>
	Public Class TrafficIncident
		#Region "Private Members"
		''' <summary>
		''' The nature of the alert. Possible values are incident and construction
		''' </summary>
		Private type_Renamed As String
		''' <summary>
		'''  A more detailed description of the alert
		''' </summary>
		Private description_Renamed As String
		''' <summary>
		''' The URL for an image corresponding to the location of the alert
		''' </summary>
		Private imageUrl_Renamed As String
		''' <summary>
		''' Incident Details
		''' </summary>
		Private incidentDetails_Renamed As IncidentDetails
		#End Region

		#Region "Public Properties"
		''' <summary>
		''' The nature of the alert. Possible values are incident and construction
		''' </summary>
		Public Property Type() As String
			Get
				Return Me.type_Renamed
			End Get
			Set(ByVal value As String)
				Me.type_Renamed = value
			End Set
		End Property
		''' <summary>
		'''  A more detailed description of the alert
		''' </summary>
		Public Property Description() As String
			Get
				Return Me.description_Renamed
			End Get
			Set(ByVal value As String)
				Me.description_Renamed = value
			End Set
		End Property
		''' <summary>
		''' The URL for an image corresponding to the location of the alert
		''' </summary>
		Public Property ImageUrl() As String
			Get
				Return Me.imageUrl_Renamed
			End Get
			Set(ByVal value As String)
				Me.imageUrl_Renamed = value
			End Set
		End Property
		''' <summary>
		''' Incident Details
		''' </summary>
		Public Property IncidentDetails() As IncidentDetails
			Get
				Return Me.incidentDetails_Renamed
			End Get
			Set(ByVal value As IncidentDetails)
				Me.incidentDetails_Renamed = value
			End Set
		End Property
		#End Region
	End Class

	''' <summary>
	''' This class represents the incident details
	''' </summary>
	Public Class IncidentDetails
		#Region "Private Members"
		''' <summary>
		''' Location where the incident took place
		''' </summary>
		Private location_Renamed As Location
		''' <summary>
		''' The direction of traffic flow affected by the alert
		''' </summary>
		Private direction_Renamed As String
		''' <summary>
		''' The severity of the alert, from 1 (least severe) to 5 (most severe)
		''' </summary>
		Private severity_Renamed As Integer
		''' <summary>
		''' The date and time this incident was reported
		''' </summary>
		Private reportDate_Renamed As DateTime
		''' <summary>
		''' The date and time this incident was last updated
		''' </summary>
		Private updateDate_Renamed As DateTime
		''' <summary>
		'''  The date and time this incident is scheduled to end
		''' </summary>
		Private endDate_Renamed As DateTime
		#End Region

		#Region "Public Properties"
		''' <summary>
		''' Location where the incident took place
		''' </summary>
		Public Property Location() As Location
			Get
				Return Me.location_Renamed
			End Get
			Set(ByVal value As Location)
				Me.location_Renamed = value
			End Set
		End Property
		''' <summary>
		''' The direction of traffic flow affected by the alert
		''' </summary>
		Public Property Direction() As String
			Get
				Return Me.direction_Renamed
			End Get
			Set(ByVal value As String)
				Me.direction_Renamed = value
			End Set
		End Property
		''' <summary>
		''' The severity of the alert, from 1 (least severe) to 5 (most severe)
		''' </summary>
		Public Property Severity() As Integer
			Get
				Return Me.severity_Renamed
			End Get
			Set(ByVal value As Integer)
				Me.severity_Renamed = value
			End Set
		End Property
		''' <summary>
		''' The date and time this incident was reported
		''' </summary>
		Public Property ReportDate() As DateTime
			Get
				Return Me.reportDate_Renamed
			End Get
			Set(ByVal value As DateTime)
				Me.reportDate_Renamed = value
			End Set
		End Property
		''' <summary>
		''' The date and time this incident was last updated
		''' </summary>
		Public Property UpdateDate() As DateTime
			Get
				Return Me.updateDate_Renamed
			End Get
			Set(ByVal value As DateTime)
				Me.updateDate_Renamed = value
			End Set
		End Property
		''' <summary>
		'''  The date and time this incident is scheduled to end
		''' </summary>
		Public Property EndDate() As DateTime
			Get
				Return Me.endDate_Renamed
			End Get
			Set(ByVal value As DateTime)
				Me.endDate_Renamed = value
			End Set
		End Property
		#End Region
	End Class
End Namespace